﻿using System;
using System.ComponentModel.Composition;
using System.ComponentModel.DataAnnotations;
using OperationPlugins.Compositions;

namespace OperationPlugins.Validators.DataAnnotations
{
    /// <summary>
    /// Used to tell that a type should be validated by using plugin validators.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = false, Inherited = true)]
    public class UsePluginValidatorsAttribute : ValidationAttribute
    {
        private static IUsePluginValidatorsAttributeSkipValidation IUsePluginValidatorsAttribute_SkipValidation { get; set; }
        private static IUsePluginValidatorsAttributeResolveOperation IUsePluginValidatorsAttribute_ResolveOperation { get; set; }
        private static IUsePluginValidatorsAttributeResolveBranch IUsePluginValidatorsAttribute_ResolveBranch { get; set; }
        private static IUsePluginValidatorsAttributeResolveOperationContextFactory IUsePluginValidatorsAttribute_ResolveOperationContextFactory { get; set; }
        private static IUsePluginValidatorsAttributeValidate IUsePluginValidatorsAttribute_Validate { get; set; }
        private static IUsePluginValidatorsAttributeAsDataAnnotationsValidationResult IUsePluginValidatorsAttribute_AsDataAnnotationsValidationResult { get; set; }

        /// <summary>
        /// Validates the specified value with respect to the current validation attribute.
        /// </summary>
        /// <param name="value">The value to validate.</param>
        /// <param name="validationContext">The context information about the validation operation.</param>
        /// <returns>An instance of the <see cref="System.ComponentModel.DataAnnotations.ValidationResult"/> class.</returns>
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var r = new ValidationResultMessageContainer();

            if (SkipValidation(value, validationContext) == false)
            {
                OperationId operation = ResolveOperation(value, validationContext);

                BranchId branch = ResolveBranch(value, validationContext);

                IOperationContextFactory contextFactory = ResolveOperationContextFactory(value, validationContext);

                ValidationResultMessageContainer container = Validate(value, operation, branch, contextFactory);

                r.Add(container);
            }

            return AsDataAnnotationsValidationResult(r);
        }


        /// <summary>
        /// Decides whether the plugins validation should be skipped.
        /// </summary>
        /// <param name="value">The value to validate.</param>
        /// <param name="validationContext">Describes the context in which a data annotations validation check is performed.</param>
        /// <returns>True if the plugins validation should be skipped, otherwise false.</returns>
        protected virtual bool SkipValidation(object value, ValidationContext validationContext)
        {
            return IUsePluginValidatorsAttribute_SkipValidation.SkipValidation(value, validationContext);
        }

        /// <summary>
        /// Resolves the current operation.
        /// </summary>
        /// <remarks>
        /// The default value is <see cref="Operations.Any"/>.
        /// </remarks>
        /// <param name="value">The value to validate.</param>
        /// <param name="validationContext">Describes the context in which a data annotations validation check is performed.</param>
        /// <returns>The current operation.</returns>
        protected virtual OperationId ResolveOperation(object value, ValidationContext validationContext)
        {
            return IUsePluginValidatorsAttribute_ResolveOperation.ResolveOperation(value, validationContext);
        }

        /// <summary>
        /// Resolves the current branch.
        /// </summary>
        /// <remarks>
        /// The default value is <see cref="Branches.Any"/>.
        /// </remarks>
        /// <param name="value">The value to validate.</param>
        /// <param name="validationContext">Describes the context in which a data annotations validation check is performed.</param>
        /// <returns>The current branch.</returns>
        protected virtual BranchId ResolveBranch(object value, ValidationContext validationContext)
        {
            return IUsePluginValidatorsAttribute_ResolveBranch.ResolveBranch(value, validationContext);
        }            

        /// <summary>
        /// Resolves the operation context factory.
        /// </summary>
        /// <param name="value">The value to validate.</param>
        /// <param name="validationContext">Describes the context in which a data annotations validation check is performed.</param>
        /// <returns>The operation context factory.</returns>
        protected virtual IOperationContextFactory ResolveOperationContextFactory(object value, ValidationContext validationContext)
        {
            return IUsePluginValidatorsAttribute_ResolveOperationContextFactory.ResolveOperationContextFactory(value, validationContext);
        }

        /// <summary>
        /// Validates an object.
        /// </summary>
        /// <param name="value">The object to be validated.</param>
        /// <param name="operation">Identifies the current operation.</param>
        /// <param name="branch">Identifies the current branch.</param>
        /// <param name="contextFactory">The IOperationContextFactory to be used.</param>
        /// <returns>The ValidationResultMessageContainer object.</returns>
        protected virtual ValidationResultMessageContainer Validate(object value, OperationId operation, BranchId branch, IOperationContextFactory contextFactory)
        {
            return IUsePluginValidatorsAttribute_Validate.Validate(value, operation, branch, contextFactory);
        }

        /// <summary>
        /// Converts a <see cref="ValidationResultMessageContainer"/> object to <see cref="System.ComponentModel.DataAnnotations.ValidationResult"/> object.
        /// </summary>
        /// <param name="container">The <see cref="ValidationResultMessageContainer"/> object.</param>
        /// <returns>The <see cref="System.ComponentModel.DataAnnotations.ValidationResult"/> object.</returns>
        protected virtual ValidationResult AsDataAnnotationsValidationResult(ValidationResultMessageContainer container)
        {
            return IUsePluginValidatorsAttribute_AsDataAnnotationsValidationResult.AsDataAnnotationsValidationResult(container);
        }            

        /// <summary>
        /// Initialize. 
        /// </summary>
        [Export(typeof(IInitializable))]
        private sealed class Initializer : IInitializable
        {
            void IInitializable.Initialize(InitializationContext context)
            {
                IUsePluginValidatorsAttribute_SkipValidation = context.CompositionContainer.GetPlugin<IUsePluginValidatorsAttributeSkipValidation>();
                IUsePluginValidatorsAttribute_ResolveOperation = context.CompositionContainer.GetPlugin<IUsePluginValidatorsAttributeResolveOperation>();
                IUsePluginValidatorsAttribute_ResolveBranch = context.CompositionContainer.GetPlugin<IUsePluginValidatorsAttributeResolveBranch>();
                IUsePluginValidatorsAttribute_ResolveOperationContextFactory = context.CompositionContainer.GetPlugin<IUsePluginValidatorsAttributeResolveOperationContextFactory>();
                IUsePluginValidatorsAttribute_Validate = context.CompositionContainer.GetPlugin<IUsePluginValidatorsAttributeValidate>();
                IUsePluginValidatorsAttribute_AsDataAnnotationsValidationResult = context.CompositionContainer.GetPlugin<IUsePluginValidatorsAttributeAsDataAnnotationsValidationResult>();
            }
        }

        [Export(typeof(IUsePluginValidatorsAttributeSkipValidation))]
        [DefaultPriority]
        private sealed class DefaultIUsePluginValidatorsAttributeSkipValidation : IUsePluginValidatorsAttributeSkipValidation
        {
            /// <summary>
            /// Decides whether the plugins validation should be skipped.
            /// </summary>
            /// <param name="value">The value to validate.</param>
            /// <param name="validationContext">Describes the context in which a data annotations validation check is performed.</param>
            /// <returns>True if the plugins validation should be skipped, otherwise false.</returns>
            bool IUsePluginValidatorsAttributeSkipValidation.SkipValidation(object value, ValidationContext validationContext)
            {
                return OperationPluginsContext.Current.DisablePluginsValidation;
            }            
        }

        [Export(typeof(IUsePluginValidatorsAttributeResolveOperation))]
        [DefaultPriority]
        private sealed class DefaultIUsePluginValidatorsAttributeResolveOperation : IUsePluginValidatorsAttributeResolveOperation
        {
            /// <summary>
            /// Resolves the current operation.
            /// </summary>
            /// <remarks>
            /// The default value is <see cref="Operations.Any"/>.
            /// </remarks>
            /// <param name="value">The value to validate.</param>
            /// <param name="validationContext">Describes the context in which a data annotations validation check is performed.</param>
            /// <returns>The current operation.</returns>
            OperationId IUsePluginValidatorsAttributeResolveOperation.ResolveOperation(object value, ValidationContext validationContext)
            {
                OperationId operation = OperationPluginsContext.Current.CurrentActionOperation;

                if (operation == null)
                {
                    var e = new ResolveOperationEventArgs(value, validationContext);

                    DataAnnotationsValidationResourceResolver.OnResolveOperation(this, e);

                    operation = e.Resolved;
                }

                return operation ?? (Operations.Any);
            }            
        }

        [Export(typeof(IUsePluginValidatorsAttributeResolveBranch))]
        [DefaultPriority]
        private sealed class DefaultIUsePluginValidatorsAttributeResolveBranch : IUsePluginValidatorsAttributeResolveBranch
        {
            /// <summary>
            /// Resolves the current branch.
            /// </summary>
            /// <remarks>
            /// The default value is <see cref="Branches.Any"/>.
            /// </remarks>
            /// <param name="value">The value to validate.</param>
            /// <param name="validationContext">Describes the context in which a data annotations validation check is performed.</param>
            /// <returns>The current branch.</returns>
            BranchId IUsePluginValidatorsAttributeResolveBranch.ResolveBranch(object value, ValidationContext validationContext)
            {
                BranchId branch = OperationPluginsContext.Current.CurrentActionBranch;

                if (branch == null)
                {
                    var e = new ResolveBranchEventArgs(value, validationContext);

                    DataAnnotationsValidationResourceResolver.OnResolveBranch(this, e);

                    branch = e.Resolved;
                }

                return branch ?? (Branches.Any);
            }            
        }

        [Export(typeof(IUsePluginValidatorsAttributeResolveOperationContextFactory))]
        [DefaultPriority]
        private sealed class DefaultIUsePluginValidatorsAttributeResolveOperationContextFactory : IUsePluginValidatorsAttributeResolveOperationContextFactory
        {
            /// <summary>
            /// Resolves the operation context factory.
            /// </summary>
            /// <param name="value">The value to validate.</param>
            /// <param name="validationContext">Describes the context in which a data annotations validation check is performed.</param>
            /// <returns>The operation context factory.</returns>
            IOperationContextFactory IUsePluginValidatorsAttributeResolveOperationContextFactory.ResolveOperationContextFactory(object value, ValidationContext validationContext)
            {
                var e = new ResolveOperationContextFactoryEventArgs(value, validationContext);

                DataAnnotationsValidationResourceResolver.OnResolveOperationContextFactory(this, e);

                IOperationContextFactory factory = e.Resolved ?? OperationContextManager.GetOperationContextFactory();

                return factory;
            }            
        }

        [Export(typeof(IUsePluginValidatorsAttributeValidate))]
        [DefaultPriority]
        private sealed class DefaultIUsePluginValidatorsAttributeValidate : IUsePluginValidatorsAttributeValidate
        {
            /// <summary>
            /// Validates an object.
            /// </summary>
            /// <param name="value">The object to be validated.</param>
            /// <param name="operation">Identifies the current operation.</param>
            /// <param name="branch">Identifies the current branch.</param>
            /// <param name="contextFactory">The IOperationContextFactory to be used.</param>
            /// <returns>The ValidationResultMessageContainer object.</returns>
            ValidationResultMessageContainer IUsePluginValidatorsAttributeValidate.Validate(object value, OperationId operation, BranchId branch, IOperationContextFactory contextFactory)
            {
                return Validator.Invoke(value, operation, branch, contextFactory);
            }            
        }

        [Export(typeof(IUsePluginValidatorsAttributeAsDataAnnotationsValidationResult))]
        [DefaultPriority]
        private sealed class DefaultIUsePluginValidatorsAttributeAsDataAnnotationsValidationResult : IUsePluginValidatorsAttributeAsDataAnnotationsValidationResult
        {
            /// <summary>
            /// Converts a <see cref="ValidationResultMessageContainer"/> object to <see cref="System.ComponentModel.DataAnnotations.ValidationResult"/> object.
            /// </summary>
            /// <param name="result">The <see cref="ValidationResultMessageContainer"/> object.</param>
            /// <returns>The <see cref="System.ComponentModel.DataAnnotations.ValidationResult"/> object.</returns>
            ValidationResult IUsePluginValidatorsAttributeAsDataAnnotationsValidationResult.AsDataAnnotationsValidationResult(ValidationResultMessageContainer result)
            {
                return result.ContainsErrors ? 
                    new ValidationResult(ValidationResultMessage.ToString(result.Errors)) : 
                    ValidationResult.Success;
            }
        }

    }
}